#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gio/gio.h>

#define NM_BUS_NAME "org.freedesktop.NetworkManager"
#define NM_OBJ_PATH "/org/freedesktop/NetworkManager"
#define NM_IFACE "org.freedesktop.NetworkManager"
#define NM_DEVICE_IFACE "org.freedesktop.NetworkManager.Device"
#define NM_DEVICE_WIFI_IFACE "org.freedesktop.NetworkManager.Device.Wireless"
#define NM_ACCESSPOINT_IFACE "org.freedesktop.NetworkManager.AccessPoint"
#define NM_SETTINGS_IFACE "org.freedesktop.NetworkManager.Settings"
#define NM_CONNECTION_IFACE "org.freedesktop.NetworkManager.Settings.Connection"

typedef struct
{
    char *ssid;
    guint8 strength;
    char *security;
    char *ap_path;
} WifiNetwork;

static GDBusConnection *conn = NULL;

// Compare function for sorting networks by signal strength (descending)
static gint compare_networks(gconstpointer a, gconstpointer b)
{
    const WifiNetwork *na = (const WifiNetwork *)a;
    const WifiNetwork *nb = (const WifiNetwork *)b;
    return (na->strength < nb->strength) ? 1 : (na->strength > nb->strength) ? -1
                                                                             : 0;
}

// Helper: Create a GDBusProxy for a specific D-Bus object
static GDBusProxy *create_proxy(const gchar *object_path, const gchar *interface)
{
    GError *error = NULL;
    GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(
        G_BUS_TYPE_SYSTEM,
        G_DBUS_PROXY_FLAGS_NONE,
        NULL, // No interface info
        NM_BUS_NAME,
        object_path,
        interface,
        NULL, // No cancellable
        &error);
    if (!proxy)
    {
        g_printerr("Failed to create proxy for %s: %s\n", object_path, error->message);
        g_error_free(error);
    }
    return proxy;
}

// Get first active Wi-Fi device path
static char *get_wifi_device_path()
{
    GDBusProxy *nm_proxy = create_proxy(NM_OBJ_PATH, NM_IFACE);
    if (!nm_proxy)
        return NULL;

    GError *error = NULL;
    GVariant *result = g_dbus_proxy_call_sync(
        nm_proxy,
        "GetDevices",
        NULL, // No arguments
        G_DBUS_CALL_FLAGS_NONE,
        -1,   // Timeout
        NULL, // No cancellable
        &error);
    if (!result)
    {
        g_printerr("Failed to get devices: %s\n", error->message);
        g_error_free(error);
        g_object_unref(nm_proxy);
        return NULL;
    }

    // Iterate devices to find Wi-Fi (type 2)
    GVariantIter *iter;
    gchar *dev_path = NULL;
    gchar *wifi_path = NULL;
    g_variant_get(result, "(ao)", &iter);

    while (g_variant_iter_next(iter, "o", &dev_path))
    {
        // Create proxy for the device
        GDBusProxy *dev_proxy = create_proxy(dev_path, NM_DEVICE_IFACE);
        if (!dev_proxy)
        {
            g_free(dev_path);
            continue;
        }

        // Get device type via proxy
        GVariant *dev_type_var = g_dbus_proxy_get_cached_property(dev_proxy, "DeviceType");
        if (dev_type_var)
        {
            guint32 dev_type;
            g_variant_get(dev_type_var, "u", &dev_type);
            if (dev_type == 2)
            { // 2 = Wi-Fi device
                wifi_path = g_strdup(dev_path);
            }
            g_variant_unref(dev_type_var);
        }

        g_object_unref(dev_proxy);
        g_free(dev_path);
        if (wifi_path)
            break; // Found Wi-Fi device
    }

    g_variant_iter_free(iter);
    g_variant_unref(result);
    g_object_unref(nm_proxy);
    return wifi_path;
}

// Scan for Wi-Fi networks
static GList *scan_wifi()
{
    char *wifi_path = get_wifi_device_path();
    if (!wifi_path)
        return NULL;

    // Create proxy for Wi-Fi device
    GDBusProxy *wifi_proxy = create_proxy(wifi_path, NM_DEVICE_WIFI_IFACE);
    if (!wifi_proxy)
    {
        g_free(wifi_path);
        return NULL;
    }

    // Trigger scan
    GError *error = NULL;
    g_dbus_proxy_call_sync(
        wifi_proxy,
        "RequestScan",
        g_variant_new("(a{sv})", NULL), // No options
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        &error);
    if (error)
    {
        g_printerr("Scan failed: %s\n", error->message);
        g_error_free(error);
        g_object_unref(wifi_proxy);
        g_free(wifi_path);
        return NULL;
    }

    // Wait 3 seconds for results
    g_print("Scanning... (3s)\n");
    g_usleep(3 * 1000 * 1000);

    // Get access points
    GVariant *ap_result = g_dbus_proxy_call_sync(
        wifi_proxy,
        "GetAllAccessPoints",
        NULL,
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        &error);
    if (!ap_result)
    {
        g_printerr("Failed to get APs: %s\n", error->message);
        g_error_free(error);
        g_object_unref(wifi_proxy);
        g_free(wifi_path);
        return NULL;
    }

    // Parse APs
    GVariantIter *ap_iter;
    gchar *ap_path;
    GList *networks = NULL;
    g_variant_get(ap_result, "(ao)", &ap_iter);

    while (g_variant_iter_next(ap_iter, "o", &ap_path))
    {
        // Create proxy for this access point
        GDBusProxy *ap_proxy = create_proxy(ap_path, NM_ACCESSPOINT_IFACE);
        if (!ap_proxy)
        {
            g_free(ap_path);
            continue;
        }

        // Get SSID (skip hidden networks)
        GVariant *ssid_var = g_dbus_proxy_get_cached_property(ap_proxy, "Ssid");
        if (!ssid_var)
        {
            g_object_unref(ap_proxy);
            g_free(ap_path);
            continue;
        }

        // Convert SSID bytes to string
        GVariantIter *ssid_iter;
        g_variant_get(ssid_var, "ay", &ssid_iter);
        GByteArray *ssid_bytes = g_byte_array_new();
        guint8 byte;
        while (g_variant_iter_next(ssid_iter, "y", &byte))
        {
            g_byte_array_append(ssid_bytes, &byte, 1);
        }
        g_variant_iter_free(ssid_iter);
        g_variant_unref(ssid_var);

        if (ssid_bytes->len == 0)
        { // Hidden network
            g_byte_array_free(ssid_bytes, TRUE);
            g_object_unref(ap_proxy);
            g_free(ap_path);
            continue;
        }
        char *ssid = g_strndup((char *)ssid_bytes->data, ssid_bytes->len);
        g_byte_array_free(ssid_bytes, TRUE);

        // Get signal strength
        GVariant *strength_var = g_dbus_proxy_get_cached_property(ap_proxy, "Strength");
        guint8 strength = 0;
        if (strength_var)
        {
            g_variant_get(strength_var, "y", &strength);
            g_variant_unref(strength_var);
        }

        // Get security type (WPA/WPA2)
        GVariant *wpa_var = g_dbus_proxy_get_cached_property(ap_proxy, "WpaFlags");
        GVariant *rsn_var = g_dbus_proxy_get_cached_property(ap_proxy, "RsnFlags");
        guint32 wpa = 0, rsn = 0;
        if (wpa_var)
        {
            g_variant_get(wpa_var, "u", &wpa);
            g_variant_unref(wpa_var);
        }
        if (rsn_var)
        {
            g_variant_get(rsn_var, "u", &rsn);
            g_variant_unref(rsn_var);
        }

        char *security = "Open";
        if (wpa || rsn)
        {
            security = (wpa && rsn) ? "WPA/WPA2" : (wpa ? "WPA" : "WPA2");
        }

        // Add to network list
        WifiNetwork *net = g_malloc(sizeof(WifiNetwork));
        net->ssid = ssid;
        net->strength = strength;
        net->security = g_strdup(security);
        net->ap_path = g_strdup(ap_path);
        networks = g_list_append(networks, net);

        g_object_unref(ap_proxy);
        g_free(ap_path);
    }

    g_variant_iter_free(ap_iter);
    g_variant_unref(ap_result);
    g_object_unref(wifi_proxy);
    g_free(wifi_path);

    // Sort by signal strength
    return g_list_sort(networks, compare_networks);
}

// Connect to a Wi-Fi network
static gboolean connect_to_wifi(const char *ssid, const char *password)
{
    char *wifi_path = get_wifi_device_path();
    if (!wifi_path)
        return FALSE;

    // Find target AP by SSID
    GList *networks = scan_wifi();
    char *ap_path = NULL;
    for (GList *l = networks; l; l = l->next)
    {
        WifiNetwork *net = l->data;
        if (strcmp(net->ssid, ssid) == 0)
        {
            ap_path = g_strdup(net->ap_path);
            break;
        }
    }
    // Free scanned networks (no longer needed)
    for (GList *l = networks; l; l = l->next)
    {
        WifiNetwork *net = l->data;
        g_free(net->ssid);
        g_free(net->security);
        g_free(net->ap_path);
        g_free(net);
    }
    g_list_free(networks);

    if (!ap_path)
    {
        g_printerr("Network '%s' not found\n", ssid);
        g_free(wifi_path);
        return FALSE;
    }

    // Step 1: Find existing connection (or create new)
    GDBusProxy *settings_proxy = create_proxy(
        "/org/freedesktop/NetworkManager/Settings",
        NM_SETTINGS_IFACE);
    if (!settings_proxy)
    {
        g_free(wifi_path);
        g_free(ap_path);
        return FALSE;
    }

    char *conn_path = NULL;
    GDBusProxy *conn_proxy = NULL;
    GError *error = NULL;

    // List all saved connections to check for existing one
    GVariant *conn_list = g_dbus_proxy_call_sync(
        settings_proxy,
        "ListConnections",
        NULL,
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        &error);
    if (conn_list)
    {
        GVariantIter *iter;
        gchar *path;
        g_variant_get(conn_list, "(ao)", &iter);
        while (g_variant_iter_next(iter, "o", &path))
        {
            // Create proxy for the saved connection
            conn_proxy = create_proxy(path, NM_CONNECTION_IFACE);
            if (conn_proxy)
            {
                // Get connection settings to check SSID (via "id" field)
                GVariant *conn_settings = g_dbus_proxy_call_sync(
                    conn_proxy,
                    "GetSettings",
                    NULL,
                    G_DBUS_CALL_FLAGS_NONE,
                    -1,
                    NULL,
                    &error);
                if (conn_settings)
                {
                    GVariant *conn_section = g_variant_lookup_value(
                        conn_settings, "connection", G_VARIANT_TYPE("a{sv}"));
                    if (conn_section)
                    {
                        GVariant *id_var = g_variant_lookup_value(
                            conn_section, "id", G_VARIANT_TYPE("s"));
                        if (id_var && strcmp(g_variant_get_string(id_var, NULL), ssid) == 0)
                        {
                            conn_path = g_strdup(path); // Found existing connection
                            g_variant_unref(id_var);
                            g_variant_unref(conn_section);
                            g_variant_unref(conn_settings);
                            break;
                        }
                        if (id_var)
                            g_variant_unref(id_var);
                        g_variant_unref(conn_section);
                    }
                    g_variant_unref(conn_settings);
                }
                // If not the target connection, free the proxy
                if (!conn_path)
                {
                    g_object_unref(conn_proxy);
                    conn_proxy = NULL;
                }
            }
            g_free(path);
            if (conn_path)
                break; // Stop if existing connection found
        }
        g_variant_iter_free(iter);
        g_variant_unref(conn_list);
    }

    // Step 2: Build connection settings (for new or update)
    GVariantBuilder *settings_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sa{sv}}"));
    if (!settings_builder)
    {
        g_printerr("Failed to create settings builder\n");
        goto cleanup;
    }

    // --------------------------
    // 1. "connection" section
    // --------------------------
    GVariantBuilder *conn_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(conn_builder, "{sv}", "type", g_variant_new_string("802-11-wireless"));
    g_variant_builder_add(conn_builder, "{sv}", "id", g_variant_new_string(ssid));
    g_variant_builder_add(conn_builder, "{sv}", "uuid", g_variant_new_string(g_strdup_printf("wifi-cli-%s-%p", ssid, (void *)wifi_path) // Unique UUID
                                                                             ));
    g_variant_builder_add(settings_builder, "{sa{sv}}", "connection", g_variant_builder_end(conn_builder));

    // --------------------------
    // 2. "802-11-wireless" section
    // --------------------------
    GVariantBuilder *wireless_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
    // Encode SSID as bytes (required by NetworkManager)
    GByteArray *ssid_bytes = g_byte_array_new();
    g_byte_array_append(ssid_bytes, (guint8 *)ssid, strlen(ssid));
    g_variant_builder_add(wireless_builder, "{sv}", "ssid", g_variant_new_from_data(G_VARIANT_TYPE("ay"), ssid_bytes->data, ssid_bytes->len, TRUE, (GDestroyNotify)g_byte_array_free, ssid_bytes));
    g_variant_builder_add(wireless_builder, "{sv}", "mode", g_variant_new_string("infrastructure"));
    g_variant_builder_add(settings_builder, "{sa{sv}}", "802-11-wireless", g_variant_builder_end(wireless_builder));

    // --------------------------
    // 3. "ipv4" section (DHCP)
    // --------------------------
    GVariantBuilder *ipv4_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(ipv4_builder, "{sv}", "method", g_variant_new_string("auto"));
    g_variant_builder_add(settings_builder, "{sa{sv}}", "ipv4", g_variant_builder_end(ipv4_builder));

    // --------------------------
    // 4. "802-11-wireless-security" (if password provided)
    // --------------------------
    if (password)
    {
        GVariantBuilder *sec_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
        g_variant_builder_add(sec_builder, "{sv}", "key-mgmt", g_variant_new_string("wpa-psk"));
        g_variant_builder_add(sec_builder, "{sv}", "psk", g_variant_new_string(password));
        g_variant_builder_add(settings_builder, "{sa{sv}}", "802-11-wireless-security", g_variant_builder_end(sec_builder));
    }

    GVariant *settings = g_variant_builder_end(settings_builder);
    if (!settings)
    {
        g_printerr("Failed to build settings\n");
        g_variant_builder_unref(settings_builder);
        goto cleanup;
    }

    // Step 3: Create or update connection
    if (!conn_path)
    {
        // Create new connection via Settings proxy
        GVariant *add_result = g_dbus_proxy_call_sync(
            settings_proxy,
            "AddConnection",
            g_variant_new("(a{sa{sv}})", settings),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);
        if (!add_result)
        {
            g_printerr("Add connection failed: %s\n", error->message);
            g_error_free(error);
            goto cleanup;
        }
        g_variant_get(add_result, "(o)", &conn_path);
        g_variant_unref(add_result);
    }
    else
    {
        // Update existing connection via its proxy
        GVariant *update_result = g_dbus_proxy_call_sync(
            conn_proxy, // Use connection-specific proxy (critical fix!)
            "Update",
            g_variant_new("(a{sa{sv}})", settings),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);
        if (!update_result)
        {
            g_printerr("Update connection failed: %s\n", error->message);
            g_error_free(error);
            goto cleanup;
        }
        g_variant_unref(update_result);
    }

    // Step 4: Activate the connection
    GDBusProxy *nm_proxy = create_proxy(NM_OBJ_PATH, NM_IFACE);
    if (!nm_proxy)
    {
        g_printerr("Failed to create NetworkManager proxy\n");
        goto cleanup;
    }

    GVariant *activate_result = g_dbus_proxy_call_sync(
        nm_proxy,
        "ActivateConnection",
        g_variant_new("(ooo)", conn_path, wifi_path, ap_path),
        G_DBUS_CALL_FLAGS_NONE,
        -1,
        NULL,
        &error);
    if (!activate_result)
    {
        g_printerr("Activate connection failed: %s\n", error->message);
        g_error_free(error);
        g_object_unref(nm_proxy);
        goto cleanup;
    }

    g_print("Successfully connected to '%s'\n", ssid);
    g_variant_unref(activate_result);
    g_object_unref(nm_proxy);

cleanup:
    // Cleanup all resources
    if (settings_builder)
        g_variant_builder_unref(settings_builder);
    if (settings)
        g_variant_unref(settings);
    if (conn_proxy)
        g_object_unref(conn_proxy);
    if (settings_proxy)
        g_object_unref(settings_proxy);
    g_free(wifi_path);
    g_free(ap_path);
    g_free(conn_path);

    return TRUE;
}

// CLI handler
int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        g_print("Usage:\n");
        g_print("  %s scan\n", argv[0]);
        g_print("  %s connect <ssid> [password]\n", argv[0]);
        return 1;
    }

    // Initialize D-Bus connection
    GError *error = NULL;
    conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
    if (!conn)
    {
        g_printerr("D-Bus connection failed: %s\n", error->message);
        g_error_free(error);
        return 1;
    }

    // Handle commands
    if (strcmp(argv[1], "scan") == 0)
    {
        GList *networks = scan_wifi();
        if (!networks)
        {
            g_print("No networks found\n");
        }
        else
        {
            g_print("\nSSID                     Signal  Security\n");
            g_print("-----------------------------------------\n");
            for (GList *l = networks; l; l = l->next)
            {
                WifiNetwork *net = l->data;
                g_print("%-25s %3hhu%%    %s\n", net->ssid, net->strength, net->security);
                g_free(net->ssid);
                g_free(net->security);
                g_free(net->ap_path);
                g_free(net);
            }
            g_list_free(networks);
        }
    }
    else if (strcmp(argv[1], "connect") == 0)
    {
        if (argc < 3)
        {
            g_printerr("Missing SSID\n");
            return 1;
        }
        const char *ssid = argv[2];
        const char *password = (argc >= 4) ? argv[3] : NULL;
        if (!connect_to_wifi(ssid, password))
        {
            return 1;
        }
    }
    else
    {
        g_printerr("Unknown command: %s\n", argv[1]);
        return 1;
    }

    g_object_unref(conn);
    return 0;
}
